﻿using System;
using System.Collections;
using System.Text;
using CI.Interfaces;
using System.Threading;
using System.Runtime.Serialization;

namespace CI.Organism
{
    /// <summary>
    /// The simple neural system is the representation of the brain, the processing part of the Organism
    /// It contains a collection of the all the neural cells and synapses
    /// 
    /// Extertnal Stimulas updates the synapes and notifies the synapes, they add any input neurons they are attached
    /// to the waiting executing list.
    /// Heart beats fires a call to the simple neural system, it creates a new waiting list and executes the current list
    /// While executing the current list any new neurons that need to be executed get added to the waiting list and it gets 
    /// excuted once the current once is finished the cycle continues until their are no populated lists remaining.
    /// 
    /// This has been tested and works correctly on single layer neural networks
    /// </summary>
    [DataContract(IsReference = true)]
    public class SimpleNeuralSystem : INeuralSystem
    {

        #region Neurons Properties

        /// <summary>Neural network list waiting to be executed</summary>        
        [DataMember]
        public INeuronCollection WaitingNeuralList { get; set; }

        /// <summary>
        /// List of neurons that where processed by the evaluate function
        /// To be used to look back at the list of neurons that have fired
        /// Clear at the begining of each time evaluate gets called
        /// </summary>
        public INeuronCollection ProcessedNeuronList
        {
            get { return _processedNeuronList ?? (_processedNeuronList = new NeuronCollection()); }
            private set
            {
                _processedNeuronList = value;
            }
        }
        private INeuronCollection _processedNeuronList;

        /// <summary>Neural network list that is currently being executed</summary>
        public INeuronCollection ExecutingNeuralList
        {
            get { return _executingNeuralList; }
        }
        [DataMember]
        private INeuronCollection _executingNeuralList;

        /// <summary>List of all the neurons in the neural system</summary>        
        public INeuronCollection Neurons
        {
            get
            {
                // if not init
                return _neurons ?? (_neurons = new NeuronCollection());
            }
            private set { _neurons = value; }
        }
        private INeuronCollection _neurons;

        /// <summary>Neural System update handler</summary>
        public event Action NeuralSystemUpdateHandler;

        /// <summary>Neural System Evaluation Start</summary>
        public event Action NotifyEvaluationStart;

        /// <summary>Neural System EvaluationStop</summary>
        public event Action NotifyEvaluationStop;

        #endregion

        #region Synapes Properties

        /// <summary>List of all the synapes in the neural system</summary>
        [DataMember]
        public ISynapseCollection Synapses
        {
            get { return _synapeses; }
            set { _synapeses = value; }
        }
        private ISynapseCollection _synapeses = new SynapseCollection();

        #endregion

        private static SimEnv SimEnv { get { return SimEnv.Instance; } }

        /// <summary>Organism the Neural system belongs to</summary>
        [DataMember]
        public IOrganism Organism { get; set; }

        // neural system heartbeat
        private UInt32 _neuralSystemHB;

        /// <summary>
        /// Default constructor that sets up the waiting and executing neural lists
        /// </summary>
        public SimpleNeuralSystem(IOrganism organism)
        {
            // set a new empty lists
            _executingNeuralList = new NeuronCollection();
            WaitingNeuralList = new NeuronCollection();
            Neurons = new NeuronCollection();
            ProcessedNeuronList = new NeuronCollection();

            // save the Organism it belongs to
            Organism = organism;

            // set the neural system in the organim to this one
            Organism.NeuralSystem = this;
        }

        /// <summary>
        /// This is the workhorse of the neural system, two part process
        /// Input Synapse - this is where it runs through the neural network waiting list executing neurons which had 
        /// input synapses fired/excited.
        /// Bound Output Synapse - this is where it runs through a list of output synapses which were excited but bound and
        /// see if their ready to fire their bound entitys
        /// </summary>
        public void Evaluate()
        {
            if (NotifyEvaluationStart != null)
                NotifyEvaluationStart();

            ProcessedNeuronList.Clear();

            // Check Neural Waiting list to see if their is anything to work on
            CheckAndEvaluateNeuralWaitingList();

            if (NotifyEvaluationStop != null)
                NotifyEvaluationStop();
        }

        private void ResetNeuronsAndSynapsesForEvaluation()
        {
            foreach (var neuron in Neurons)
            {
                neuron.SummedInputValue = 0;
            }

            foreach (var synapse in Synapses)
            {
                synapse.ResetValues();
            }

            var synapseIOtoReset = Organism.GetIOManifest();
            if (synapseIOtoReset != null)
            {
                foreach (var synapseIO in synapseIOtoReset)
                {
                    synapseIO.Reset(_neuralSystemHB);
                }
            }

        }

        public void AddToWaitingList(INeuron neuron)
        {
            if (Monitor.TryEnter(WaitingNeuralList, Globals.CriticalAcquireLockTimeout))
            {
                try
                {
                    // check to see if neuron is already in the list   
                    if (!(WaitingNeuralList.Contains(neuron)))
                    {
                        SimEnv.TraceLogger.LogDebug(string.Format("Adding neuron {0} to the waiting list", ((ICell)neuron).Guid));
                        WaitingNeuralList.Add(neuron);
                    }
                    else
                    {
                        SimEnv.TraceLogger.LogDebug(string.Format("Neuron {0} already exists in waiting list", ((ICell)neuron).Guid));
                    }
                }
                finally
                {
                    Monitor.Exit(WaitingNeuralList);
                }
            }
            else
            {
                throw new Exception("Unable to acquire lock on WaitingNeuralList");
            }
        }

        /// <summary>
        /// Check Neural Waiting list to see if their is anything to work on
        /// </summary>
        private void CheckAndEvaluateNeuralWaitingList()
        {
            SimEnv.TraceLogger.LogDebug(string.Format("Part 1, Check Neural Waiting list to see if their is anything to work on"));

            // make sure the list is valid , actually if its null we cant lock it
            if (WaitingNeuralList == null)
                throw new NerualSystemException("WaitingNeuralList can not be null");

            // if their are any neurons to execute
            while (WaitingNeuralList.Count > 0 && !SimEnv.BreakExit)
            {

                // increment the neural system heartbeat
                _neuralSystemHB++;

                ResetNeuronsAndSynapsesForEvaluation();

                // clear the executing list
                _executingNeuralList.Clear();

                SimEnv.TraceLogger.LogDebug("Processing Waiting List : " + WaitingNeuralList.Count);

                // lock the waiting list and move them into the executing list
                MoveWaitingListToExecutingList();

                SimEnv.TraceLogger.LogDebug("Processing Executing List : " + ExecutingNeuralList.Count);

                // lock the executing list and run with them
                ExecutateNeuronActivationFunctions();
            }
        }

        private void ExecutateNeuronActivationFunctions()
        {
            if (Monitor.TryEnter(ExecutingNeuralList, Globals.CriticalAcquireLockTimeout))
            {
                try
                {
                    // now lets look through the neurons testing their activation
                    foreach (Neuron neuron in ExecutingNeuralList)
                    {
                        SimEnv.TraceLogger.LogDebug("Neuron Activation Function : " + neuron.Guid);
                        neuron.ActivationFunction(_neuralSystemHB);
                    }
                }
                finally
                {
                    Monitor.Exit(ExecutingNeuralList);
                }
            }
            else
            {
                throw new NerualSystemException("Unable to acquire lock on ExecutingNeuralList");
            }
        }

        private void MoveWaitingListToExecutingList()
        {
            if (Monitor.TryEnter(WaitingNeuralList, Globals.CriticalAcquireLockTimeout))
            {
                try
                {
                    lock (WaitingNeuralList)
                    {
                        // the executing list is loaded from whats waiting
                        foreach (INeuron neuron in WaitingNeuralList)
                        {
                            if (!(ExecutingNeuralList.Contains(neuron)))
                            {
                                ProcessedNeuronList.Add(neuron);
                                if (ProcessedNeuronList.Count > 1000)
                                {
                                    // todo : refactor nnet so this can't happen...
                                    SimEnv.TriggerScenarioRestart("Neural meltdown");
                                    return;
                                }

                                ExecutingNeuralList.Add(neuron);
                            }
                        }

                        SimEnv.TraceLogger.LogDebug("Clearing WAITING list for org : " + Organism.Guid);

                        // create a new empty one and release the lock
                        WaitingNeuralList.Clear();
                    }
                }
                finally
                {
                    Monitor.Exit(WaitingNeuralList);
                }
            }
            else
            {
                throw new NerualSystemException("Unable to acquire lock on WaitingNeuralList");
            }
        }

        /// <summary>
        /// Add a neuron to the neural system, This will register synapes for binding in the environment   
        /// </summary>
        /// <param name="neuron">Neuron to register</param>
        public void AddNeuron(INeuron neuron)
        {

            // check first to make sure the neuron is not already present
            if (Neurons.Find(((ICell)neuron).Guid) != null)
            {
                // one already exists therefore the genes must be defective, kill it off
                Organism.Terminate("Attempting to add already existing Neuron :" + ((ICell)neuron).Guid);
                return;
            }

            // add it to the list            
            Neurons.Add(neuron);


            foreach (var synapse in neuron.InputSynapse)
            {
                if (synapse.SourceNotification != null)
                    synapse.SourceNotification.ActivationEvent += synapse.FireInput;

                AddSynapse(synapse);
            }


            foreach (var synapse in neuron.OutputSynapse)
            {

                neuron.ActivationEvent += synapse.FireInput;
                AddSynapse(synapse);
            }

            NeuralSystemUpdates();

        }

        private void AddSynapse(ISynapse synapes)
        {
            // add it to the neural systems list of synapses if it hasnt been added
            if (Synapses.Find(synapes.Guid) == null)
                Synapses.Add(synapes);
        }

        private void NeuralSystemUpdates()
        {
            if (NeuralSystemUpdateHandler != null)
                NeuralSystemUpdateHandler();
        }

        /// <summary>
        /// Delete a neuron from the neural system
        /// </summary>
        /// <param name="neuron">neuron to remove</param>
        public void DelNeuron(INeuron neuron)
        {
            // unregister the input synapes in the environment
            foreach (var synapse in neuron.InputSynapse)
            {
                if (synapse == null || synapse.SourceNotification == null)
                    continue;

                synapse.SourceNotification.ActivationEvent -= synapse.FireInput;
                DeleteSynapse(synapse);
            }

            // unregister the output synapes in the environment
            foreach (var synapse in neuron.OutputSynapse)
            {
                neuron.ActivationEvent -= synapse.FireInput;
                DeleteSynapse(synapse);
            }

            // remove the neuron from sns
            Neurons.Remove(neuron);

            NeuralSystemUpdates();

        }

        private void DeleteSynapse(ISynapse synapes)
        {
            // delete it from the neural systems list of synapses if it exists
            if (Synapses.Find(synapes.Guid) == null)
                Synapses.Remove(synapes);
        }

        /// <summary>
        /// Mutate the neural system
        /// 1 - Copy the input and output synapse and mutate the learning rule as well, note: this is done now on cell mutation now
        /// 2 - Mutate further and add and delete synapses
        /// </summary>
        /// <param name="mutationRate">Mutation rate</param>
        /// <param name="parentOrganism">Parent Organism to use as a guide</param>
        public void Mutate(double mutationRate, IOrganism parentOrganism)
        {
            // mutate making new synapse
            if (Mutation.MutateState(mutationRate))
            {
                // create a new synapse, this will bind its neurons
                Synapses.Add(Synapse.CreateNewSynapse(mutationRate, Organism));
            }

            // mutate removing some synapse
            if (Mutation.MutateState(mutationRate) && (Synapses.Count > 0))
            {
                // synapse to remove
                var removeSynapse = Synapses[Mutation.CollectionSelect((ICollection)Synapses)];

                // take it away from the source neuron
                if (removeSynapse.SourceNotification != null)
                {
                    var sourceNeuron = Neurons.Find(removeSynapse.SourceNotification.Guid);
                    if (sourceNeuron != null)
                    {
                        Neurons.Remove(sourceNeuron);
                    }
                }

                // take it away from the target neurons
                if (removeSynapse.TargetNotification != null)
                {
                    var targetNeuron = Neurons.Find(removeSynapse.TargetNotification.Guid);
                    if (targetNeuron != null)
                    {
                        Neurons.Remove(targetNeuron);
                    }
                }
                Synapses.Remove(removeSynapse);
            }

        }

        /// <summary>
        /// To string that will return the count of neurons and synapse's
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            var result = new StringBuilder();
            result.AppendLine(base.ToString());
            result.AppendLine("Neurons count : " + Neurons.Count);
            result.Append("Synapse count : " + Synapses.Count);
            return result.ToString();
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            ProcessedNeuronList.Clear();

            foreach (var synapse in Synapses)
            {
                synapse.Dispose();
            }

            foreach (var neuron in Neurons)
            {
                neuron.Dispose();
            }

            foreach (var synapseIO in Organism.GetIOManifest())
            {
                synapseIO.Dispose();
            }


            Synapses.Clear();
            Neurons.Clear();

            NeuralSystemUpdateHandler = null;
            NotifyEvaluationStart = null;
            NotifyEvaluationStop = null;

        }

        /// <summary>Neural System Exception</summary>
        public class NerualSystemException : Exception
        {
            /// <summary>
            /// Neural System Exception
            /// </summary>
            /// <param name="message">Exception Message</param>
            public NerualSystemException(string message) : base(message) { }
        }
    }
}
